#!/usr/bin/python3
# -*- coding: utf-8 -*-


def create_proxy(
        underlying,
        invoker
):
    if underlying is None:
        raise TypeError('Illegal underlying, must not be None')
    if not callable(invoker):
        raise TypeError('Illegal invoker, must be callable')

    return _ClassProxy(underlying, invoker)


class _ClassProxy:
    def __init__(
            self,
            underlying,
            invoker
    ):
        self.__underlying = underlying
        self.__invoker = invoker

    def __getattr__(
            self,
            item
    ):
        r = getattr(self.__underlying, item, None)

        return _MethodProxy(self.underlying, r, item, self.invoker) if callable(r) else r

    @property
    def underlying(self):
        return self.__underlying

    @property
    def invoker(self):
        return self.__invoker


class _MethodProxy:
    __slots__ = ('__caller', '__method', '__name', '__invoker',)

    def __init__(
            self,
            caller,
            method,
            name,
            invoker
    ):
        self.__caller = caller
        self.__method = method
        self.__name = name
        self.__invoker = invoker

    def __call__(
            self,
            *args,
            **kwargs
    ):
        return self.invoker(_MethodCallee(self.caller, self.method, self.name, args, kwargs))

    @property
    def caller(self):
        return self.__caller

    @property
    def method(self):
        return self.__method

    @property
    def invoker(self):
        return self.__invoker

    @property
    def name(self):
        return self.__name

    def __str__(self):
        return '{caller}.{method} [invoker={invoker}]'.format(caller=self.caller, method=self.method.__name__,
                                                              invoker=self.invoker)


class _MethodCallee:
    __slots__ = ('__caller', '__method', '__name', '__args', '__kwargs')

    def __init__(
            self,
            caller,
            method,
            name,
            args,
            kwargs
    ):
        self.__caller = caller
        self.__method = method
        self.__name = name
        self.__args = tuple(args) if args else tuple()
        self.__kwargs = dict(kwargs) if kwargs else dict()

    @property
    def caller(self):
        return self.__caller

    @property
    def method(self):
        return self.__method

    @property
    def name(self):
        return self.__name

    def name_is(
            self,
            name: str
    ):
        """ 判断需要执行的底层方法名是否是指定的名字。

        :param name:  指定的方法名。
        :return: 需要执行的底层方法名是否是指定的名字。
        """
        return self.name == str(name)

    def getarg(
            self,
            name
    ):
        if isinstance(name, int):
            return self.__args[name] if name < len(self.__args) else None
        else:
            return self.__kwargs.get(name, None)

    def __str__(self):
        return 'callee[{caller}.{method}]'.format(caller=self.caller, method=self.method.__name__)

    def execute(self):
        return self.method(*self.__args, **self.__kwargs)
